<!DOCTYPE html>
<html lang=zh>
<head>
    <meta charset="utf-8">
    
    <title>第四十五章：基于SpringBoot 设计业务逻辑异常统一处理 | 恒宇少年De成长之路</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
    <meta name="description" content="在我们平时的项目研发过程中，异常一般都是程序员最为头疼的问题，异常的抛出、捕获、处理等既涉及事务回滚，还会涉及返回前端消息提醒信息。那么我们怎么设计可以解决上面的两个的痛点呢？我们可不可以统一处理业务逻辑然后给出前端对应的异常提醒内容呢？">
<meta name="keywords" content="SpringBoot">
<meta property="og:type" content="article">
<meta property="og:title" content="第四十五章：基于SpringBoot 设计业务逻辑异常统一处理">
<meta property="og:url" content="http://blog.yuqiyu.com/spring-boot-chapter-45/index.html">
<meta property="og:site_name" content="恒宇少年De成长之路">
<meta property="og:description" content="在我们平时的项目研发过程中，异常一般都是程序员最为头疼的问题，异常的抛出、捕获、处理等既涉及事务回滚，还会涉及返回前端消息提醒信息。那么我们怎么设计可以解决上面的两个的痛点呢？我们可不可以统一处理业务逻辑然后给出前端对应的异常提醒内容呢？">
<meta property="og:locale" content="zh-CN">
<meta property="og:image" content="http://upload-images.jianshu.io/upload_images/4461954-d80b777ca38632ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240">
<meta property="og:updated_time" content="2018-01-23T15:58:16.533Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="第四十五章：基于SpringBoot 设计业务逻辑异常统一处理">
<meta name="twitter:description" content="在我们平时的项目研发过程中，异常一般都是程序员最为头疼的问题，异常的抛出、捕获、处理等既涉及事务回滚，还会涉及返回前端消息提醒信息。那么我们怎么设计可以解决上面的两个的痛点呢？我们可不可以统一处理业务逻辑然后给出前端对应的异常提醒内容呢？">
<meta name="twitter:image" content="http://upload-images.jianshu.io/upload_images/4461954-d80b777ca38632ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240">
    

    
        <link rel="alternate" href="/" title="恒宇少年De成长之路" type="application/atom+xml" />
    

    

    <link rel="stylesheet" href="/libs/font-awesome/css/font-awesome.min.css">
    <link rel="stylesheet" href="/libs/open-sans/styles.css">
    <link rel="stylesheet" href="/libs/source-code-pro/styles.css">

    <link rel="stylesheet" href="/css/style.css">

    <script src="/libs/jquery/2.1.3/jquery.min.js"></script>
    
    
        <link rel="stylesheet" href="/libs/lightgallery/css/lightgallery.min.css">
    
    
        <link rel="stylesheet" href="/libs/justified-gallery/justifiedGallery.min.css">
    
    
    
    


</head>

<body>
    <div id="container">
        <header id="header">
    <div id="header-main" class="header-inner">
        <div class="outer">
            <a href="/" id="logo">
                <i class="logo"></i>
                <span class="site-title">恒宇少年De成长之路</span>
            </a>
            <nav id="main-nav">
                
                    <a class="main-nav-link" href="/.">首页</a>
                
                    <a class="main-nav-link" href="/archives">时间轴</a>
                
                    <a class="main-nav-link" href="/categories">文章专题</a>
                
                    <a class="main-nav-link" href="/tags">标签云</a>
                
                    <a class="main-nav-link" href="/about">关于我</a>
                
            </nav>
            
                
                <nav id="sub-nav">
                    <div class="profile" id="profile-nav">
                        <a id="profile-anchor" href="javascript:;">
                            <img class="avatar" src="/css/images/avatar.png" />
                            <i class="fa fa-caret-down"></i>
                        </a>
                    </div>
                </nav>
            
            <div id="search-form-wrap">

    <form class="search-form">
        <input type="text" class="ins-search-input search-form-input" placeholder="搜索" />
        <!--button type="submit" class="search-form-submit"></button-->
    </form>
    <div class="ins-search">
    <div class="ins-search-mask"></div>
    <div class="ins-search-container">
        <div class="ins-input-wrapper">
            <input type="text" class="ins-search-input" placeholder="想要查找什么..." />
            <span class="ins-close ins-selectable"><i class="fa fa-times-circle"></i></span>
        </div>
        <div class="ins-section-wrapper">
            <div class="ins-section-container"></div>
        </div>
    </div>
</div>
<script>
(function (window) {
    var INSIGHT_CONFIG = {
        TRANSLATION: {
            POSTS: '文章',
            PAGES: '页面',
            CATEGORIES: '分类',
            TAGS: '标签',
            UNTITLED: '(未命名)',
        },
        ROOT_URL: '/',
        CONTENT_URL: '/content.json',
    };
    window.INSIGHT_CONFIG = INSIGHT_CONFIG;
})(window);
</script>
<script src="/js/insight.js"></script>

</div>
        </div>
    </div>
    <div id="main-nav-mobile" class="header-sub header-inner">
        <table class="menu outer">
            <tr>
                
                    <td ><a  class="main-nav-link" href="/.">首页</a></td>
                
                    <td ><a  class="main-nav-link" href="/archives">时间轴</a></td>
                
                    <td ><a  class="main-nav-link" href="/categories">文章专题</a></td>
                
                    <td ><a  class="main-nav-link" href="/tags">标签云</a></td>
                
                    <td ><a  class="main-nav-link" href="/about">关于我</a></td>
                
                <td>
                    
    <div class="search-form">
        <input type="text" class="ins-search-input search-form-input" placeholder="搜索" />
    </div>

                </td>
            </tr>
        </table>
    </div>
</header>

        <div class="outer">
            
                

<aside id="profile">
    <div class="inner profile-inner">
        <div class="base-info profile-block">
            <img id="avatar" src="/css/images/avatar.png" />
            <h2 id="name">恒宇少年</h2>
            <h3 id="title">Java软件工程师 &amp; 程序猿</h3>
            <span id="location"><i class="fa fa-map-marker"></i>山东, 济南</span>
            <a id="follow" target="_blank" href="https://www.jianshu.com/u/092df3f77bca">关注我</a>
        </div>
        <div class="article-info profile-block">
            <div class="article-info-block">
                43
                <span>文章</span>
            </div>
            <div class="article-info-block">
                2
                <span>标签</span>
            </div>
        </div>
        
        <div class="profile-block social-links">
            <table>
                <tr>
                    
                    
                    <td>
                        <a href="http://github.com/ppoffice/hexo-theme-icarus" target="_blank" title="github" class=tooltip>
                            <i class="fa fa-github"></i>
                        </a>
                    </td>
                    
                    <td>
                        <a href="/" target="_blank" title="twitter" class=tooltip>
                            <i class="fa fa-twitter"></i>
                        </a>
                    </td>
                    
                    <td>
                        <a href="/" target="_blank" title="facebook" class=tooltip>
                            <i class="fa fa-facebook"></i>
                        </a>
                    </td>
                    
                    <td>
                        <a href="/" target="_blank" title="dribbble" class=tooltip>
                            <i class="fa fa-dribbble"></i>
                        </a>
                    </td>
                    
                    <td>
                        <a href="/" target="_blank" title="rss" class=tooltip>
                            <i class="fa fa-rss"></i>
                        </a>
                    </td>
                    
                </tr>
            </table>
        </div>
        
    </div>
</aside>

            
            <section id="main"><article id="post-spring-boot-chapter-45" class="article article-type-post" itemscope itemprop="blogPost">
    <div class="article-inner">
        
        
            <header class="article-header">
                
    
        <h1 class="article-title" itemprop="name">
            第四十五章：基于SpringBoot 设计业务逻辑异常统一处理
        </h1>
    

                
                    <div class="article-meta">
                        
    <div class="article-date">
        <i class="fa fa-calendar"></i>
        <a href="/spring-boot-chapter-45/">
            <time datetime="2018-01-08T16:00:00.000Z" itemprop="datePublished">2018-01-09</time>
        </a>
    </div>


                        
    <div class="article-category">
    	<i class="fa fa-folder"></i>
        <a class="article-category-link" href="/categories/SpringBoot-核心技术/">SpringBoot 核心技术</a>
    </div>

                        
    <div class="article-tag">
        <i class="fa fa-tag"></i>
        <a class="tag-link" href="/tags/SpringBoot/">SpringBoot</a>
    </div>

                    </div>
                
            </header>
        
        
        <div class="article-entry" itemprop="articleBody">
        
            
            <p>在我们平时的项目研发过程中，异常一般都是程序员最为头疼的问题，异常的抛出、捕获、处理等既涉及事务回滚，还会涉及返回前端消息提醒信息。那么我们怎么设计可以解决上面的两个的痛点呢？我们可不可以统一处理业务逻辑然后给出前端对应的异常提醒内容呢？<br><a id="more"></a></p>
<h1 id="本章目标"><a href="#本章目标" class="headerlink" title="本章目标"></a>本章目标</h1><p>基于<code>SpringBoot</code>平台构建业务逻辑异常统一处理，异常消息内容格式化。</p>
<h1 id="SpringBoot-企业级核心技术学习专题"><a href="#SpringBoot-企业级核心技术学习专题" class="headerlink" title="SpringBoot 企业级核心技术学习专题"></a>SpringBoot 企业级核心技术学习专题</h1><table>
<thead>
<tr>
<th style="text-align:center">专题</th>
<th style="text-align:left">专题名称</th>
<th style="text-align:left">专题描述</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:center">001</td>
<td style="text-align:left"><a href="http://www.jianshu.com/c/3f69deddbed3" target="_blank" rel="noopener">Spring Boot 核心技术</a></td>
<td style="text-align:left">讲解SpringBoot一些企业级层面的核心组件</td>
</tr>
<tr>
<td style="text-align:center">002</td>
<td style="text-align:left"><a href="https://gitee.com/hengboy/spring-boot-chapter" target="_blank" rel="noopener">Spring Boot 核心技术章节源码</a></td>
<td style="text-align:left">Spring Boot 核心技术简书每一篇文章码云对应源码</td>
</tr>
<tr>
<td style="text-align:center">003</td>
<td style="text-align:left"><a href="http://www.jianshu.com/c/1faac22666e7" target="_blank" rel="noopener">Spring Cloud 核心技术</a></td>
<td style="text-align:left">对Spring Cloud核心技术全面讲解</td>
</tr>
<tr>
<td style="text-align:center">004</td>
<td style="text-align:left"><a href="https://gitee.com/hengboy/spring-cloud-chapter" target="_blank" rel="noopener">Spring Cloud 核心技术章节源码</a></td>
<td style="text-align:left">Spring Cloud 核心技术简书每一篇文章对应源码</td>
</tr>
<tr>
<td style="text-align:center">005</td>
<td style="text-align:left"><a href="http://www.jianshu.com/c/ab4789177827" target="_blank" rel="noopener">QueryDSL 核心技术</a></td>
<td style="text-align:left">全面讲解QueryDSL核心技术以及基于SpringBoot整合SpringDataJPA</td>
</tr>
<tr>
<td style="text-align:center">006</td>
<td style="text-align:left"><a href="http://www.jianshu.com/c/f1b269bb2fd6" target="_blank" rel="noopener">SpringDataJPA 核心技术</a></td>
<td style="text-align:left">全面讲解SpringDataJPA核心技术</td>
</tr>
</tbody>
</table>
<h1 id="构建项目"><a href="#构建项目" class="headerlink" title="构建项目"></a>构建项目</h1><p>我们将逻辑异常核心处理部分提取出来作为单独的<code>jar</code>供其他模块引用，创建项目在<code>parent</code>项目<code>pom.xml</code>添加公共使用的依赖，配置内容如下所示：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependencies&gt;</span><br><span class="line">        &lt;!--Lombok--&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;lombok&lt;/artifactId&gt;</span><br><span class="line">            &lt;optional&gt;true&lt;/optional&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">        &lt;!--测试模块依赖--&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-test&lt;/artifactId&gt;</span><br><span class="line">            &lt;scope&gt;test&lt;/scope&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">        &lt;!--web依赖--&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-web&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">&lt;/dependencies&gt;</span><br></pre></td></tr></table></figure></p>
<p>项目创建完成后除了<code>.idea</code>、<code>iml</code>、<code>pom.xml</code>保留，其他的都删除。</p>
<h3 id="异常处理核心子模块"><a href="#异常处理核心子模块" class="headerlink" title="异常处理核心子模块"></a>异常处理核心子模块</h3><p>我们创建一个名为<code>springboot-core-exception</code>的子模块，在该模块内自定义一个<code>LogicException</code>运行时异常类，继承<code>RuntimeException</code>并重写构造函数，代码如下所示：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 自定义业务逻辑异常类</span><br><span class="line"> * ========================</span><br><span class="line"> * Created with IntelliJ IDEA.</span><br><span class="line"> * User：恒宇少年</span><br><span class="line"> * Date：2018/1/7</span><br><span class="line"> * Time：下午2:38</span><br><span class="line"> * 码云：http://git.oschina.net/jnyqy</span><br><span class="line"> * ========================</span><br><span class="line"> *</span><br><span class="line"> * @author yuqiyu</span><br><span class="line"> */</span><br><span class="line">public class LogicException extends RuntimeException &#123;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 日志对象</span><br><span class="line">     */</span><br><span class="line">    private Logger logger = LoggerFactory.getLogger(LogicException.class);</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 错误消息内容</span><br><span class="line">     */</span><br><span class="line">    protected String errMsg;</span><br><span class="line">    /**</span><br><span class="line">     * 错误码</span><br><span class="line">     */</span><br><span class="line">    protected String errCode;</span><br><span class="line">    /**</span><br><span class="line">     * 格式化错误码时所需参数列表</span><br><span class="line">     */</span><br><span class="line">    protected String[] params;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 获取错误消息内容</span><br><span class="line">     * 根据errCode从redis内获取未被格式化的错误消息内容</span><br><span class="line">     * 并通过String.format()方法格式化错误消息以及参数</span><br><span class="line">     *</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    public String getErrMsg() &#123;</span><br><span class="line">        return errMsg;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 获取错误码</span><br><span class="line">     *</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    public String getErrCode() &#123;</span><br><span class="line">        return errCode;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 获取异常参数列表</span><br><span class="line">     *</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    public String[] getParams() &#123;</span><br><span class="line">        return params;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 构造函数设置错误码以及错误参数列表</span><br><span class="line">     *</span><br><span class="line">     * @param errCode 错误码</span><br><span class="line">     * @param params  错误参数列表</span><br><span class="line">     */</span><br><span class="line">    public LogicException(String errCode, String... params) &#123;</span><br><span class="line">        this.errCode = errCode;</span><br><span class="line">        this.params = params;</span><br><span class="line">        //获取格式化后的异常消息内容</span><br><span class="line">        this.errMsg = ErrorMessageTools.getErrorMessage(errCode, params);</span><br><span class="line">        //错误信息</span><br><span class="line">        logger.error(&quot;系统遇到如下异常，异常码：&#123;&#125;&gt;&gt;&gt;异常信息：&#123;&#125;&quot;, errCode, errMsg);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>在重写的构造函数内需要传递两个参数<code>errCode</code>、<code>params</code>，其目的是为了初始化类内的全局变量。</p>
<ul>
<li><code>errCode</code>：该字段是对应的异常码，我们在后续文章内容中创建一个存放异常错误码的枚举，而<code>errCode</code>就是枚举对应的字符串的值。</li>
<li><code>params</code>：这里是对应<code>errCode</code>字符串含义描述时所需要的参数列表。</li>
<li><code>errMsg</code>：格式化后的业务逻辑异常消息描述，我们在构造函数内可以看到调用了<code>ErrorMessageTools.getErrorMessage(errCode,params);</code>，这个方法作用是通过异常码在数据库内获取未格式化的异常描述，通过传递的参数进行格式化异常消息描述。</li>
</ul>
<blockquote>
<p>创建异常核心包的目的就是让其他模块直接添加依赖，那异常描述内容该怎么获取呢？</p>
</blockquote>
<h4 id="定义异常消息获取接口"><a href="#定义异常消息获取接口" class="headerlink" title="定义异常消息获取接口"></a>定义异常消息获取接口</h4><p>我们在<code>springboot-exception-core</code>模块内添加一个接口<code>LogicExceptionMessage</code>，该接口提供通过异常码获取未格式化的异常消息描述内容方法，接口定义如下所示：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 逻辑异常接口定义</span><br><span class="line"> * 使用项目需要实现该接口方法并提供方法实现</span><br><span class="line"> * errCode对应逻辑异常码</span><br><span class="line"> * getMessage返回字符串为逻辑异常消息内容</span><br><span class="line"> * ========================</span><br><span class="line"> * Created with IntelliJ IDEA.</span><br><span class="line"> * User：恒宇少年</span><br><span class="line"> * Date：2018/1/7</span><br><span class="line"> * Time：下午2:41</span><br><span class="line"> * 码云：http://git.oschina.net/jnyqy</span><br><span class="line"> * ========================</span><br><span class="line"> * @author yuqiyu</span><br><span class="line"> */</span><br><span class="line">public interface LogicExceptionMessage &#123;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 获取异常消息内容</span><br><span class="line">     * @param errCode 错误码</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    public String getMessage(String errCode);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>在需要加载<code>springboot-exception-core</code>依赖的项目中，创建实体类实现<code>LogicExceptionMessage</code>接口并重写<code>getMessage(String errCode)</code>方法我们就可以通过<code>spring IOC</code>获取实现类实例进行操作获取数据，下面我们在编写使用异常模块时会涉及到。</p>
<h4 id="格式化异常消息工具类"><a href="#格式化异常消息工具类" class="headerlink" title="格式化异常消息工具类"></a>格式化异常消息工具类</h4><p>下面我们再回头看看构造函数格式化异常消息工具类<code>ErrorMessageTools</code>，该工具类内提供<code>getErrorMessage</code>方法用于获取格式化后的异常消息描述，代码实现如下所示：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 异常消息描述格式化工具类</span><br><span class="line"> * ========================</span><br><span class="line"> * Created with IntelliJ IDEA.</span><br><span class="line"> * User：恒宇少年</span><br><span class="line"> * Date：2018/1/7</span><br><span class="line"> * Time：下午2:40</span><br><span class="line"> * 码云：http://git.oschina.net/jnyqy</span><br><span class="line"> * ========================</span><br><span class="line"> *</span><br><span class="line"> * @author yuqiyu</span><br><span class="line"> */</span><br><span class="line">public class ErrorMessageTools &#123;</span><br><span class="line">    /**</span><br><span class="line">     * 异常消息获取</span><br><span class="line">     *</span><br><span class="line">     * @param errCode 异常消息码</span><br><span class="line">     * @param params  格式化异常参数所需参数列表</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    public static String getErrorMessage(String errCode, Object... params) &#123;</span><br><span class="line">        //获取业务逻辑消息实现</span><br><span class="line">        LogicExceptionMessage logicExceptionMessage = SpringBeanTools.getBean(LogicExceptionMessage.class);</span><br><span class="line">        if (ObjectUtils.isEmpty(logicExceptionMessage)) &#123;</span><br><span class="line">            try &#123;</span><br><span class="line">                throw new Exception(&quot;请配置实现LogicExceptionMessage接口并设置实现类被SpringIoc所管理。&quot;);</span><br><span class="line">            &#125; catch (Exception e) &#123;</span><br><span class="line">                e.printStackTrace();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        //获取错误消息内容</span><br><span class="line">        String errMsg = logicExceptionMessage.getMessage(errCode);</span><br><span class="line">        //格式化错误消息内容</span><br><span class="line">        return ObjectUtils.isEmpty(params) ? errMsg : String.format(errMsg, params);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<blockquote>
<p>注意：由于我们的工具类都是静态方法调用方式，所以无法直接使用<code>Spring IOC</code>注解注入的方式获取<code>LogicExceptionMessage</code>实例。</p>
</blockquote>
<p>由于无法注入实例，在<code>getErrorMessage</code>方法内，我们通过工具类<code>SpringBeanTools</code>来获取<code>ApplicationContext</code>上下文实例，再通过上下文来获取指定类型的<code>Bean</code>；获取到<code>LogicExceptionMessage</code>实例后调用<code>getMessage</code>方法，根据传入的<code>errCode</code>就可以直接从接口实现类实例中获取到未格式化的异常描述！</p>
<blockquote>
<p>当然实现类可以是以<code>Redis</code>、<code>Map集合</code>、<code>数据库</code>、<code>文本</code>作为数据来源。</p>
</blockquote>
<p>获取到未格式化的异常描述后通过<code>String.format</code>方法以及传递的参数直接就可以获取格式化后的字符串，如：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">未格式化异常消息 =&gt; 用户：%s已被冻结，无法操作.</span><br><span class="line">格式化代码 =&gt; String.format(&quot;%s已被冻结，无法操作.&quot;,&quot;恒宇少年&quot;);</span><br><span class="line">格式化后效果 =&gt; 用户：恒宇少年已被冻结，无法操作.</span><br></pre></td></tr></table></figure></p>
<p>具体的格式化特殊字符含义可以去查看<code>String.format</code>文档，如何获取<code>ApplicationContext</code>上下文对象，请访问<a href="https://www.jianshu.com/p/3cd2d4e73eb7" target="_blank" rel="noopener">第三十二章：如何获取SpringBoot项目的applicationContext对象</a>查看。</p>
<p>我们再回到<code>LogicException</code>构造函数内，这时<code>errMsg</code>字段对应的值就会是格式化后的异常消息描述，在外部我们调用<code>getErrMsg</code>方法就可以直接得到异常描述。</p>
<p>到目前为止，我们已经将<code>springboot-exception-core</code>模块代码编码完成，下面我们来看下怎么来使用我们自定义的业务逻辑异常并且获取格式化后的异常消息描述。</p>
<h3 id="异常示例模块"><a href="#异常示例模块" class="headerlink" title="异常示例模块"></a>异常示例模块</h3><p>基于<code>parent</code>我们来创建一个名为<code>springboot-exception-example</code>的子模块项目，项目内需要添加一些额外的配置依赖，当然也需要将我们的<code>springboot-exception-core</code>依赖添加进入，<code>pom.xml</code>配置文件内容如下所示：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">&lt;dependencies&gt;</span><br><span class="line">        &lt;!--异常核心依赖--&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;com.hengyu&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;springboot-exception-core&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;0.0.1-SNAPSHOT&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">        &lt;!--spring data jpa依赖--&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;spring-boot-starter-data-jpa&lt;/artifactId&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">        &lt;!--数据库驱动--&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;mysql&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;mysql-connector-java&lt;/artifactId&gt;</span><br><span class="line">            &lt;scope&gt;runtime&lt;/scope&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">        &lt;!--druid依赖--&gt;</span><br><span class="line">        &lt;dependency&gt;</span><br><span class="line">            &lt;groupId&gt;com.alibaba&lt;/groupId&gt;</span><br><span class="line">            &lt;artifactId&gt;druid-spring-boot-starter&lt;/artifactId&gt;</span><br><span class="line">            &lt;version&gt;1.1.6&lt;/version&gt;</span><br><span class="line">        &lt;/dependency&gt;</span><br><span class="line">&lt;/dependencies&gt;</span><br></pre></td></tr></table></figure></p>
<p>下面我们来配置下我们示例项目<code>application.yml</code>文件需要的配置，如下所示：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">spring:</span><br><span class="line">  application:</span><br><span class="line">    name: springboot-exception-core</span><br><span class="line">    #数据源配置</span><br><span class="line">  datasource:</span><br><span class="line">    druid:</span><br><span class="line">      url: jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;autoReconnect=true</span><br><span class="line">      username: root</span><br><span class="line">      password: 123456</span><br><span class="line">      driver-class-name: com.mysql.jdbc.Driver</span><br><span class="line">  jpa:</span><br><span class="line">    properties:</span><br><span class="line">      hibernate:</span><br><span class="line">        #配置显示sql</span><br><span class="line">        show_sql: true</span><br><span class="line">        #配置格式化sql</span><br><span class="line">        format_sql: true</span><br></pre></td></tr></table></figure></p>
<p>在上面我们有讲到<code>LogicExceptionMessage</code>获取的内容可以从很多种数据源中读取，我们还是采用数据库来进行读取，建议正式环境放到<code>redis</code>缓存内！！！</p>
<h4 id="异常信息表"><a href="#异常信息表" class="headerlink" title="异常信息表"></a>异常信息表</h4><p>接下来在数据库内创建异常信息表<code>sys_exception_info</code>，语句如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">DROP TABLE IF EXISTS `sys_exception_info`;</span><br><span class="line">/*!40101 SET @saved_cs_client     = @@character_set_client */;</span><br><span class="line">/*!40101 SET character_set_client = utf8 */;</span><br><span class="line">CREATE TABLE `sys_exception_info` (</span><br><span class="line">  `EI_ID` int(11) NOT NULL AUTO_INCREMENT COMMENT &apos;主键自增&apos;,</span><br><span class="line">  `EI_CODE` varchar(30) DEFAULT NULL COMMENT &apos;异常码&apos;,</span><br><span class="line">  `EI_MESSAGE` varchar(50) DEFAULT NULL COMMENT &apos;异常消息内容&apos;,</span><br><span class="line">  PRIMARY KEY (`EI_ID`)</span><br><span class="line">) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT=&apos;系统异常基本信息&apos;;</span><br><span class="line">/*!40101 SET character_set_client = @saved_cs_client */;</span><br><span class="line"></span><br><span class="line">--</span><br><span class="line">-- Dumping data for table `sys_exception_info`</span><br><span class="line">--</span><br><span class="line"></span><br><span class="line">LOCK TABLES `sys_exception_info` WRITE;</span><br><span class="line">/*!40000 ALTER TABLE `sys_exception_info` DISABLE KEYS */;</span><br><span class="line">INSERT INTO `sys_exception_info` VALUES (1,&apos;USER_NOT_FOUND&apos;,&apos;用户不存在.&apos;),(2,&apos;USER_STATUS_FAILD&apos;,&apos;用户状态异常.&apos;);</span><br><span class="line">/*!40000 ALTER TABLE `sys_exception_info` ENABLE KEYS */;</span><br><span class="line">UNLOCK TABLES;</span><br></pre></td></tr></table></figure></p>
<p>我们通过<code>spring-data-jpa</code>来实现数据读取，下面对应数据表创建对应的<code>Entity</code>。</p>
<h4 id="异常信息实体"><a href="#异常信息实体" class="headerlink" title="异常信息实体"></a>异常信息实体</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 系统异常基本信息实体</span><br><span class="line"> * ========================</span><br><span class="line"> * Created with IntelliJ IDEA.</span><br><span class="line"> * User：恒宇少年</span><br><span class="line"> * Date：2018/1/7</span><br><span class="line"> * Time：下午3:35</span><br><span class="line"> * 码云：http://git.oschina.net/jnyqy</span><br><span class="line"> * ========================</span><br><span class="line"> * @author yuqiyu</span><br><span class="line"> */</span><br><span class="line">@Data</span><br><span class="line">@Entity</span><br><span class="line">@Table(name = &quot;sys_exception_info&quot;)</span><br><span class="line">public class ExceptionInfoEntity implements Serializable&#123;</span><br><span class="line">    /**</span><br><span class="line">     * 异常消息编号</span><br><span class="line">     */</span><br><span class="line">    @Id</span><br><span class="line">    @GeneratedValue</span><br><span class="line">    @Column(name = &quot;EI_ID&quot;)</span><br><span class="line">    private Integer id;</span><br><span class="line">    /**</span><br><span class="line">     * 异常消息错误码</span><br><span class="line">     */</span><br><span class="line">    @Column(name = &quot;EI_CODE&quot;)</span><br><span class="line">    private String code;</span><br><span class="line">    /**</span><br><span class="line">     * 异常消息内容</span><br><span class="line">     */</span><br><span class="line">    @Column(name = &quot;EI_MESSAGE&quot;)</span><br><span class="line">    private String message;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h4 id="异常信息数据接口"><a href="#异常信息数据接口" class="headerlink" title="异常信息数据接口"></a>异常信息数据接口</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 异常数据接口定义</span><br><span class="line"> * ========================</span><br><span class="line"> * Created with IntelliJ IDEA.</span><br><span class="line"> * User：恒宇少年</span><br><span class="line"> * Date：2018/1/7</span><br><span class="line"> * Time：下午3:34</span><br><span class="line"> * 码云：http://git.oschina.net/jnyqy</span><br><span class="line"> * ========================</span><br><span class="line"> * @author yuqiyu</span><br><span class="line"> */</span><br><span class="line">public interface ExceptionRepository</span><br><span class="line">    extends JpaRepository&lt;ExceptionInfoEntity,Integer&gt;</span><br><span class="line">&#123;</span><br><span class="line">    /**</span><br><span class="line">     * 根据异常码获取异常配置信息</span><br><span class="line">     * @param code 异常码</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    ExceptionInfoEntity findTopByCode(String code);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>在数据接口内通过<code>spring-data-jpa</code>方法查询方式，通过<code>errCode</code>读取异常信息实体内容。</p>
<p>在开发过程中异常跑出时所用到的<code>errCode</code>一般存放在枚举类型或者常量接口内，在这里我们选择可扩展相对来说比较强的<code>枚举类型</code>，代码如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 错误码枚举类型</span><br><span class="line"> * ========================</span><br><span class="line"> * Created with IntelliJ IDEA.</span><br><span class="line"> * User：恒宇少年</span><br><span class="line"> * Date：2018/1/7</span><br><span class="line"> * Time：下午3:25</span><br><span class="line"> * 码云：http://git.oschina.net/jnyqy</span><br><span class="line"> * ========================</span><br><span class="line"> * @author yuqiyu</span><br><span class="line"> */</span><br><span class="line">public enum ErrorCodeEnum &#123;</span><br><span class="line">    /**</span><br><span class="line">     * 用户不存在.</span><br><span class="line">     */</span><br><span class="line">    USER_NOT_FOUND,</span><br><span class="line">    /**</span><br><span class="line">     * 用户状态异常.</span><br><span class="line">     */</span><br><span class="line">    USER_STATUS_FAILD,</span><br><span class="line">    //...添加其他错误码</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>异常码枚举内容项是需要根据数据库异常信息表对应变动的，能够保证我们在抛出异常时，在数据库内有对应的信息。  </p>
<h4 id="LogicExceptionMessage实现类定义"><a href="#LogicExceptionMessage实现类定义" class="headerlink" title="LogicExceptionMessage实现类定义"></a>LogicExceptionMessage实现类定义</h4><p>我们在<code>springboot-exception-core</code>核心模块内添加了<code>LogicExceptionMessage</code>接口定义，需要我们实现该接口的<code>getMessage</code>方法核心模块，这样才可以获取数据库内对应的异常信息，实现类如下所示：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 业务逻辑异常消息获取实现类</span><br><span class="line"> * - 消息可以从数据库内获取</span><br><span class="line"> * - 消息可从Redis内获取</span><br><span class="line"> * ========================</span><br><span class="line"> * Created with IntelliJ IDEA.</span><br><span class="line"> * User：恒宇少年</span><br><span class="line"> * Date：2018/1/7</span><br><span class="line"> * Time：下午3:16</span><br><span class="line"> * 码云：http://git.oschina.net/jnyqy</span><br><span class="line"> * ========================</span><br><span class="line"> * @author yuqiyu</span><br><span class="line"> */</span><br><span class="line">@Component</span><br><span class="line">public class LogicExceptionMessageSupport implements LogicExceptionMessage &#123;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 异常数据接口</span><br><span class="line">     */</span><br><span class="line">    @Autowired</span><br><span class="line">    private ExceptionRepository exceptionRepository;</span><br><span class="line"></span><br><span class="line">    /**</span><br><span class="line">     * 根据错误码获取错误信息</span><br><span class="line">     * @param errCode 错误码</span><br><span class="line">     * @return</span><br><span class="line">     */</span><br><span class="line">    @Override</span><br><span class="line">    public String getMessage(String errCode) &#123;</span><br><span class="line">        ExceptionInfoEntity exceptionInfoEntity = exceptionRepository.findTopByCode(errCode);</span><br><span class="line">        if(!ObjectUtils.isEmpty(exceptionInfoEntity)) &#123;</span><br><span class="line">            return exceptionInfoEntity.getMessage();</span><br><span class="line">        &#125;</span><br><span class="line">        return &quot;系统异常&quot;;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>在<code>getMessage</code>方法内通过<code>ExceptionRepository</code>数据接口定义的<code>findTopByCode</code>方法获取指定异常吗的异常信息，当存在异常信息时返回未格式化的异常描述。</p>
<h4 id="统一返回实体定义"><a href="#统一返回实体定义" class="headerlink" title="统一返回实体定义"></a>统一返回实体定义</h4><p>对于接口项目（包括前后分离项目）在处理返回统一格式时，我们通常会采用固定实体的方式，这样对于前端调用接口的开发者来说解析内容是比较方便的，同样在开发过程中会约定遇到系统异常、业务逻辑异常时返回的格式内容，当然这跟请求接口正确返回的格式是一样的，只不过字段内容有差异。<br>统一返回实体<code>ApiResponseEntity&lt;T extends Object&gt;</code>如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * 接口响应实体</span><br><span class="line"> * ========================</span><br><span class="line"> * Created with IntelliJ IDEA.</span><br><span class="line"> * User：恒宇少年</span><br><span class="line"> * Date：2018/1/9</span><br><span class="line"> * Time：下午3:04</span><br><span class="line"> * 码云：http://git.oschina.net/jnyqy</span><br><span class="line"> * ========================</span><br><span class="line"> * @author yuqiyu</span><br><span class="line"> */</span><br><span class="line">@Data</span><br><span class="line">@Builder</span><br><span class="line">public class ApiResponseEntity&lt;T extends Object&gt; &#123;</span><br><span class="line">    /**</span><br><span class="line">     * 错误消息</span><br><span class="line">     */</span><br><span class="line">    private String errorMsg;</span><br><span class="line">    /**</span><br><span class="line">     * 数据内容</span><br><span class="line">     */</span><br><span class="line">    private T data;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></p>
<p>在<code>ApiResponseEntity</code>实体内，采用了<code>Lombok</code>的构造者设计模式<code>@Builder</code>注解，配置该注解的实体会自动在<code>.class</code>文件内添加内部类实现设计模式，部分自动生成代码如下：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">// ...</span><br><span class="line">public static class ApiResponseEntityBuilder&lt;T&gt; &#123;</span><br><span class="line">        private String errorMsg;</span><br><span class="line">        private T data;</span><br><span class="line"></span><br><span class="line">        ApiResponseEntityBuilder() &#123;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        public ApiResponseEntity.ApiResponseEntityBuilder&lt;T&gt; errorMsg(String errorMsg) &#123;</span><br><span class="line">            this.errorMsg = errorMsg;</span><br><span class="line">            return this;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        public ApiResponseEntity.ApiResponseEntityBuilder&lt;T&gt; data(T data) &#123;</span><br><span class="line">            this.data = data;</span><br><span class="line">            return this;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        public ApiResponseEntity&lt;T&gt; build() &#123;</span><br><span class="line">            return new ApiResponseEntity(this.errorMsg, this.data);</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        public String toString() &#123;</span><br><span class="line">            return &quot;ApiResponseEntity.ApiResponseEntityBuilder(errorMsg=&quot; + this.errorMsg + &quot;, data=&quot; + this.data + &quot;)&quot;;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">// ...</span><br><span class="line">```  </span><br><span class="line">到目前为止，我们并未添加全局异常相关的配置，而全局异常配置这块，我们采用之前章节讲到的`@ControllerAdvice`来实现，`@ControllerAdvice`相关的内容请访问[第二十一章：SpringBoot项目中的全局异常处理](https://www.jianshu.com/p/1c6207d8ee9d)。</span><br><span class="line"></span><br><span class="line">#### 全局异常通知定义</span><br><span class="line">我们本章节仅仅添加业务逻辑异常的处理，具体编码如下所示：</span><br></pre></td></tr></table></figure></p>
<p>/**</p>
<ul>
<li>控制器异常通知类</li>
<li>========================</li>
<li>Created with IntelliJ IDEA.</li>
<li>User：恒宇少年</li>
<li>Date：2018/1/7</li>
<li>Time：下午5:30</li>
<li>码云：<a href="http://git.oschina.net/jnyqy" target="_blank" rel="noopener">http://git.oschina.net/jnyqy</a></li>
<li>========================<br>*</li>
<li><p>@author yuqiyu<br>*/<br>@ControllerAdvice(annotations = RestController.class)<br>@ResponseBody<br>public class ExceptionAdvice {</p>
<p> /**</p>
<ul>
<li><p>logback new instance<br>*/<br>Logger logger = LoggerFactory.getLogger(this.getClass());</p>
<p>/**</p>
</li>
<li>处理业务逻辑异常<br>*</li>
<li>@param e 业务逻辑异常对象实例</li>
<li>@return 逻辑异常消息内容<br>*/<br>@ExceptionHandler(LogicException.class)<br>@ResponseStatus(code = HttpStatus.OK)<br>public ApiResponseEntity<string> logicException(LogicException e) {<br> logger.error(“遇到业务逻辑异常：【{}】”, e.getErrCode());<br> // 返回响应实体内容<br> return ApiResponseEntity.<string>builder().errorMsg(e.getErrMsg()).build();<br>}<br>}<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">&gt; 最近技术群内有同学问我，既然我们用的是`@RestController`为什么这里还需要配置`@ResponseBody`？这里给大家一个解释，我们控制器通知确实是监听的`@RestController`，而`@RestController`注解的控制器统一都是返回`JSON`格式的数据。那么我们在遇到异常后，请求已经不再控制器内了，已经交付给控制器通知类，那么我们通知类如果同样想返回`JSON`数据，这里就需要配置`@ResponseBody`注解来实现。</span><br><span class="line"></span><br><span class="line">我们来看上面`logicException()`方法，该方法返回值是我们定义的统一返回实体，目的是为了遇到业务逻辑异常时同样返回与正确请求一样的格式。</span><br><span class="line">- `@ ExceptionHandler `配置了将要处理`LogicException`类型的异常，也就是只要系统遇到`LogicException`异常并且抛给了控制器，就会调用该方法。</span><br><span class="line">- `@ResponseStatus`配置了返回的状态值，因为我们遇到业务逻辑异常前端肯定需要的不是500错误，而是一个200状态的`JSON`业务异常描述。</span><br><span class="line"></span><br><span class="line">在方法返回时使用`构造者设计模式`并将异常消息传递给`errorMsg()`方法，这样就实现了字段`errorMsg`的赋值。</span><br><span class="line"></span><br><span class="line"># 测试</span><br><span class="line">异常相关的编码完成，下面我们来创建一个测试的控制器模拟业务逻辑发生时，系统是怎么做出的返回？</span><br><span class="line">测试控制内容如下所示：</span><br></pre></td></tr></table></figure>
</string></string></li>
</ul>
</li>
</ul>
<p>/**</p>
<ul>
<li>测试控制器</li>
<li>========================</li>
<li>Created with IntelliJ IDEA.</li>
<li>User：恒宇少年</li>
<li>Date：2018/1/7</li>
<li>Time：下午3:12</li>
<li>码云：<a href="http://git.oschina.net/jnyqy" target="_blank" rel="noopener">http://git.oschina.net/jnyqy</a></li>
<li>========================<br>*</li>
<li>@author yuqiyu<br><em>/<br>@RestController<br>public class IndexController {<br> /*</em><ul>
<li>首页方法<br>*</li>
<li>@return<br><em>/<br>@RequestMapping(value = “/index”)<br>public ApiResponseEntity<string> index() throws LogicException {<br> /*</string></em><ul>
<li>模拟用户不存在</li>
<li>抛出业务逻辑异常<br>*/<br>if (true) {<br> throw new LogicException(ErrorCodeEnum.USER_STATUS_FAILD.toString());<br>}<br>return ApiResponseEntity.<string>builder().data(“this is index mapping”).build();<br>}<br>}<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">根据上面代码含义，当我们在访问`/index`时就会发生`USER_STATUS_FAILD`业务逻辑异常，按照我们之前的全局异常配置以及统一返回实体实例化，访问后会出现`ApiResponseEntity`格式`JSON`数据，下面我们运行项目访问查看效果。</span><br><span class="line">界面输出内容如下所示：</span><br></pre></td></tr></table></figure>
</string></li>
</ul>
</li>
</ul>
</li>
</ul>
<p>{<br>    “errorMsg”: “用户状态异常.”,<br>    “data”: null<br>}<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">而在控制台由于我们编写了日志信息，也同样有对应的输出，如下所示：</span><br></pre></td></tr></table></figure></p>
<p>Hibernate:<br>    select<br>        exceptioni0_.ei_id as ei_id1<em>0</em>,<br>        exceptioni0_.ei_code as ei_code2<em>0</em>,<br>        exceptioni0_.ei_message as ei_messa3<em>0</em><br>    from<br>        sys_exception<em>info exceptioni0</em><br>    where<br>        exceptioni0_.ei_code=? limit ?<br>2018-01-09 18:54:00.647 ERROR 2024 — [nio-8080-exec-1] c.h.s.exception.core.LogicException      : 系统遇到如下异常，异常码：USER_STATUS_FAILD&gt;&gt;&gt;异常信息：用户状态异常.<br>2018-01-09 18:54:00.649 ERROR 2024 — [nio-8080-exec-1] c.h.s.e.c.advice.ExceptionAdvice         : 遇到业务逻辑异常：【USER_STATUS_FAILD】<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&gt; 如果业务逻辑异常在`Service`层时，我们根本不需要去操心事务回滚的问题，因为`LogicException`本身就是运行时异常，而项目中抛出运行时异常时事务就会自动回滚。</span><br><span class="line"></span><br><span class="line">我们把业务逻辑异常屏蔽掉，把`true`改成`false`查看正确时返回的格式，如下所示：</span><br></pre></td></tr></table></figure></p>
<p>{<br>    “errorMsg”: null,<br>    “data”: “this is index mapping”<br>}<br><code>``
如果想把对应的</code>null`改成空字符串，请访问查看<a href="https://www.jianshu.com/p/14df78573cb2" target="_blank" rel="noopener">第五章：配置使用FastJson返回Json视图</a>。</p>
<h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>本章将之前章节的部分内容进行了整合，主要是全局异常、统一格式返回等；这种方式是目前我们公司产品中正在使用的方式，已经可以满足平时的业务逻辑异常定义以及返回，将异常消息存放到<code>数据库</code>中我们可以随时更新提示内容，这一点还是比较易用的。</p>
<p>本章源码已经上传到码云：<br>SpringBoot配套源码地址：<a href="https://gitee.com/hengboy/spring-boot-chapter" target="_blank" rel="noopener">https://gitee.com/hengboy/spring-boot-chapter</a><br>SpringCloud配套源码地址：<a href="https://gitee.com/hengboy/spring-cloud-chapter" target="_blank" rel="noopener">https://gitee.com/hengboy/spring-cloud-chapter</a><br>SpringBoot相关系列文章请访问：<a href="http://www.jianshu.com/p/9a08417e4e84" target="_blank" rel="noopener">目录：SpringBoot学习目录</a><br>QueryDSL相关系列文章请访问：<a href="http://www.jianshu.com/p/99a5ec5c3bd5" target="_blank" rel="noopener">QueryDSL通用查询框架学习目录</a><br>SpringDataJPA相关系列文章请访问：<a href="http://www.jianshu.com/p/615ed9c1fe84" target="_blank" rel="noopener">目录：SpringDataJPA学习目录</a>，感谢阅读！<br>欢迎加入QQ技术交流群，共同进步。<br><img src="http://upload-images.jianshu.io/upload_images/4461954-d80b777ca38632ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240" alt="QQ技术交流群"></p>

        
        </div>
        <footer class="article-footer">
            <div class="share-container">


    <div class="bdsharebuttonbox">
    <a href="#" class="bds_more" data-cmd="more">分享到：</a>
    <a href="#" class="bds_qzone" data-cmd="qzone" title="分享到QQ空间">QQ空间</a>
    <a href="#" class="bds_tsina" data-cmd="tsina" title="分享到新浪微博">新浪微博</a>
    <a href="#" class="bds_tqq" data-cmd="tqq" title="分享到腾讯微博">腾讯微博</a>
    <a href="#" class="bds_renren" data-cmd="renren" title="分享到人人网">人人网</a>
    <a href="#" class="bds_weixin" data-cmd="weixin" title="分享到微信">微信</a>
</div>
<script>
window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{"bdSize":16}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];
</script>
<style>
    .bdshare_popup_box {
        border-radius: 4px;
        border: #e1e1e1 solid 1px;
    }
    .bdshare-button-style0-16 a,
    .bdshare-button-style0-16 .bds_more {
        padding-left: 20px;
        margin: 6px 10px 6px 0;
    }
    .bdshare_dialog_list a,
    .bdshare_popup_list a,
    .bdshare_popup_bottom a {
        font-family: 'Microsoft Yahei';
    }
    .bdshare_popup_top {
        display: none;
    }
    .bdshare_popup_bottom {
        height: auto;
        padding: 5px;
    }
</style>


</div>

            
    

        </footer>
    </div>
    
        
<nav id="article-nav">
    
        <a href="/spring-boot-chapter-46/" id="article-nav-newer" class="article-nav-link-wrap">
            <strong class="article-nav-caption">上一篇</strong>
            <div class="article-nav-title">
                
                    第四十六章：SpringBoot &amp; RabbitMQ完成消息延迟消费
                
            </div>
        </a>
    
    
        <a href="/spring-boot-chapter-44/" id="article-nav-older" class="article-nav-link-wrap">
            <strong class="article-nav-caption">下一篇</strong>
            <div class="article-nav-title">第四十四章： 基于SpringBoot &amp; AOP完成统一资源自动查询映射</div>
        </a>
    
</nav>


    
</article>


    
    

</section>
            
			
				
<aside id="sidebar">
   
        
    <div class="widget-wrap">
        <h3 class="widget-title">最新文章</h3>
        <div class="widget">
            <ul id="recent-post" class="no-thumbnail">
                
                    <li>
                        
                        <div class="item-inner">
                            <p class="item-category"><a class="article-category-link" href="/categories/SpringBoot-核心技术/">SpringBoot 核心技术</a></p>
                            <p class="item-title"><a href="/spring-boot-chapter-52/" class="title">第五十二章：基于SpringBoot2使用Rest访问MongoDB数据</a></p>
                            <p class="item-date"><time datetime="2018-04-21T16:00:00.000Z" itemprop="datePublished">2018-04-22</time></p>
                        </div>
                    </li>
                
                    <li>
                        
                        <div class="item-inner">
                            <p class="item-category"><a class="article-category-link" href="/categories/SpringBoot-核心技术/">SpringBoot 核心技术</a></p>
                            <p class="item-title"><a href="/spring-boot-chapter-51/" class="title">第五十一章：基于SpringBoot2 &amp; MongoDB完成自动化集成</a></p>
                            <p class="item-date"><time datetime="2018-04-15T16:00:00.000Z" itemprop="datePublished">2018-04-16</time></p>
                        </div>
                    </li>
                
                    <li>
                        
                        <div class="item-inner">
                            <p class="item-category"><a class="article-category-link" href="/categories/SpringBoot-核心技术/">SpringBoot 核心技术</a></p>
                            <p class="item-title"><a href="/spring-boot-chapter-50/" class="title">第五十章：SpringBoot2.0新特性 - 岂止至今最简单redis缓存集成</a></p>
                            <p class="item-date"><time datetime="2018-04-14T16:00:00.000Z" itemprop="datePublished">2018-04-15</time></p>
                        </div>
                    </li>
                
                    <li>
                        
                        <div class="item-inner">
                            <p class="item-category"><a class="article-category-link" href="/categories/SpringBoot-核心技术/">SpringBoot 核心技术</a></p>
                            <p class="item-title"><a href="/spring-boot-chapter-49/" class="title">第四十九章：SpringBoot2.0新特性 - 你get到WebMvcConfigurer两种配置方式了吗？</a></p>
                            <p class="item-date"><time datetime="2018-03-16T16:00:00.000Z" itemprop="datePublished">2018-03-17</time></p>
                        </div>
                    </li>
                
                    <li>
                        
                        <div class="item-inner">
                            <p class="item-category"><a class="article-category-link" href="/categories/SpringBoot-核心技术/">SpringBoot 核心技术</a></p>
                            <p class="item-title"><a href="/spring-boot-chapter-48/" class="title">第四十八章：SpringBoot2.0新特性 - RabbitMQ信任package设置</a></p>
                            <p class="item-date"><time datetime="2018-03-12T16:00:00.000Z" itemprop="datePublished">2018-03-13</time></p>
                        </div>
                    </li>
                
            </ul>
        </div>
    </div>

    
        
    <div class="widget-wrap">
        <h3 class="widget-title">分类</h3>
        <div class="widget">
            <ul class="category-list"><li class="category-list-item"><a class="category-list-link" href="/categories/QueryDSL-核心技术/">QueryDSL 核心技术</a><span class="category-list-count">7</span></li><li class="category-list-item"><a class="category-list-link" href="/categories/SpringBoot-核心技术/">SpringBoot 核心技术</a><span class="category-list-count">36</span></li></ul>
        </div>
    </div>

    
        
    <div class="widget-wrap">
        <h3 class="widget-title">归档</h3>
        <div class="widget">
            <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/04/">四月 2018</a><span class="archive-list-count">3</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/03/">三月 2018</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2018/01/">一月 2018</a><span class="archive-list-count">1</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/12/">十二月 2017</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/11/">十一月 2017</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/10/">十月 2017</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/09/">九月 2017</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/08/">八月 2017</a><span class="archive-list-count">4</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/07/">七月 2017</a><span class="archive-list-count">9</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/06/">六月 2017</a><span class="archive-list-count">2</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/05/">五月 2017</a><span class="archive-list-count">3</span></li><li class="archive-list-item"><a class="archive-list-link" href="/archives/2017/04/">四月 2017</a><span class="archive-list-count">5</span></li></ul>
        </div>
    </div>

    
        
    <div class="widget-wrap">
        <h3 class="widget-title">标签</h3>
        <div class="widget">
            <ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/QueryDSL/">QueryDSL</a><span class="tag-list-count">7</span></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/SpringBoot/">SpringBoot</a><span class="tag-list-count">43</span></li></ul>
        </div>
    </div>

    
        
    <div class="widget-wrap">
        <h3 class="widget-title">标签云</h3>
        <div class="widget tagcloud">
            <a href="/tags/QueryDSL/" style="font-size: 10px;">QueryDSL</a> <a href="/tags/SpringBoot/" style="font-size: 20px;">SpringBoot</a>
        </div>
    </div>

    
        
    <div class="widget-wrap widget-list">
        <h3 class="widget-title">链接</h3>
        <div class="widget">
            <ul>
                
                    <li>
     				<a href="http://hexo.io" target="_blank">Hexo</a>
					</li>
                
            </ul>
        </div>
    </div>


    
    <div id="toTop" class="fa fa-angle-up"></div>
</aside>

				
        </div>
        <!--引入不蒜子-->
<script async src="//dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<footer id="footer">
    <div class="outer">
        <div id="footer-info" class="inner">
            <span  id="busuanzi_container_site_pv">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
            &copy; 2017 - 2018 恒宇少年 - 版权所有<br>
        </div>
    </div>
</footer>

        


    
        <script src="/libs/lightgallery/js/lightgallery.min.js"></script>
        <script src="/libs/lightgallery/js/lg-thumbnail.min.js"></script>
        <script src="/libs/lightgallery/js/lg-pager.min.js"></script>
        <script src="/libs/lightgallery/js/lg-autoplay.min.js"></script>
        <script src="/libs/lightgallery/js/lg-fullscreen.min.js"></script>
        <script src="/libs/lightgallery/js/lg-zoom.min.js"></script>
        <script src="/libs/lightgallery/js/lg-hash.min.js"></script>
        <script src="/libs/lightgallery/js/lg-share.min.js"></script>
        <script src="/libs/lightgallery/js/lg-video.min.js"></script>
    
    
        <script src="/libs/justified-gallery/jquery.justifiedGallery.min.js"></script>
    
    



<!-- Custom Scripts -->
<script src="/js/main.js"></script>

    </div>
</body>
</html>